using System;
using System.IO;
using System.Xml;
using System.Net;
using System.Threading;
using System.Collections;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text.RegularExpressions;

using Gtk;

using repragator;

public partial class MainWindow: Gtk.Window
{	
	private TextBuffer buffer;
	private Thread debugMessageThread;
	private repragator.Printer printer;
	private Dictionary<string, Macro> macros;
	private string currentFile = string.Empty; 
	private string userHome = (Environment.OSVersion.Platform == PlatformID.MacOSX ||
	                           Environment.OSVersion.Platform == PlatformID.Unix)
		? Environment.GetEnvironmentVariable("HOME") + "/.repragator/"
		: Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%") + "\\Local Settings\\Application Data\\repragator\\";
	
	public MainWindow (): 
		base (Gtk.WindowType.Toplevel)
	{
		Build ();

		this.printer = new repragator.Printer();
		
		this.printer.DebugMessageEvent += new DebugMessageHandler(this.DebugMessage);
		
		this.printer.FinishedParsingEvent += new FinishedParsingHandler(this.RenderLayer);
		
		foreach (string port in this.printer.AvailablePorts)
		{
			this.portComboBox.AppendText(port);	
		}
		
		this.portComboBox.Active = 0;
		
		this.buffer = this.debugTextView.Buffer;
		
		this.macros = new Dictionary<string, Macro>();
		
		this.LoadSettings();
		
		this.debugMessageThread = new Thread(new ThreadStart(this.DebugMessageThread));
		
		this.debugMessageThread.Start();
	}
	
	protected void OnDeleteEvent (object sender, DeleteEventArgs a)
	{		
		this.debugMessageThread.Abort();
		
		if (this.printer != null)
			this.printer.Disconnect();
		
		this.SaveSettings();
		
		Application.Quit ();
		
		a.RetVal = true;
	}
	
	protected void OnExitMenuActivated (object sender, System.EventArgs e)
	{		
		this.debugMessageThread.Abort();
		
		if (this.printer != null)
			this.printer.Disconnect();
		
		this.SaveSettings();
		
		Application.Quit();
	}
	
	protected void DebugMessage(string message)
	{
		try
		{
			TextTag tag = new TextTag(null);
			
			this.buffer.TagTable.Add(tag);
			
			TextIter iter = this.buffer.GetIterAtLine(this.buffer.LineCount);
			
			this.buffer.InsertWithTags(ref iter, message + "\n", tag);
		}
		catch (Exception)
		{
			
		}
	}
	
	private void DebugMessageThread()
	{
		try
		{
			string temp = string.Empty, hotend = string.Empty, heatedbed = string.Empty;
			
			if (File.Exists(System.IO.Path.GetTempPath() + "repragator.tmp"))
				File.Delete(System.IO.Path.GetTempPath() + "repragator.tmp");
			
			using (FileStream fs = new FileStream(System.IO.Path.GetTempPath() + "repragator.tmp", FileMode.Create, FileAccess.ReadWrite, FileShare.Write))
			{	
				using (StreamReader reader = new StreamReader(fs))
				{
					while (true)
					{
						while (reader.EndOfStream)
							Thread.Sleep(100);
						
						temp = reader.ReadLine();
						
						if (temp != null && temp.Contains("T:"))
						{
							hotend = Regex.Match(temp, "T:[0-9]+(?:\\.[0-9]*)?").Value;
							
							if (hotend != null && hotend != string.Empty)
								this.hotendLabel.Text = hotend.Substring(2);
							
							heatedbed = Regex.Match(temp, "B:[0-9]+(?:\\.[0-9]*)?").Value;
							
							if (heatedbed != null && heatedbed != string.Empty)
								this.heatedbedLabel.Text = heatedbed.Substring(2);
							
							continue;
						}
						
						if (temp != null && temp != string.Empty && temp != "ok")
							this.DebugMessage(temp);
						
						Thread.Sleep(100);
					}
				}
			}
		}
		catch (Exception e)
		{
			this.DebugMessage(e.Message + "\n" + e.StackTrace);	
		}
	}
	
	protected void RenderLayer()
	{
		new Thread(new ThreadStart(delegate
		{
			this.gcodeLayerView.Render(this.printer.RenderableGCode);
		})).Start();
	}
	
	protected void OnOpenMenuActivated (object sender, EventArgs e)
	{
		FileChooserDialog dialog = new FileChooserDialog("Open file...", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept);
		
		FileFilter filter = new FileFilter();
		
		filter.AddPattern("*.gcode");
		
		dialog.AddFilter(filter);
		
		dialog.SelectMultiple = false;
		
		if (dialog.Run() == (int)ResponseType.Accept)
		{
			this.currentFile = dialog.Filename;
			
			new Thread(new ThreadStart(delegate
			{
				this.printer.Load(dialog.Filename);
			})).Start();
		}
		
		dialog.Destroy();
	}

	protected void OnRescanButtonClicked (object sender, System.EventArgs e)
	{		
		this.portComboBox.Model = new ListStore(typeof(string));
		
		foreach (string port in this.printer.AvailablePorts)
		{
			this.portComboBox.AppendText(port);	
		}
		
		this.portComboBox.Active = 0;
	}

	protected void OnMovementbuttonboxMovementButtonClickedEvent (object sender, repragator.MovementButtonArgs e)
	{
		this.printer.Move(e.Axis, e.Distance, e.Feedrate);
	}

	protected void OnConnectButtonClicked (object sender, System.EventArgs e)
	{
		bool exist = false;
		
		// janky way to tell if value exists already, adding a value to ComboBoxEntry doesn't add it to ListStore
		foreach (object[] item in (ListStore)this.baudComboBox.Model)
		{
			if (this.baudComboBox.ActiveText == item[0].ToString())
			{
				exist = true;
				
				break;
			}
		}
		
		if (!exist)
		{
			this.baudComboBox.AppendText(this.baudComboBox.ActiveText);
		}
		
		if (this.printer.Connect(this.portComboBox.ActiveText, int.Parse(this.baudComboBox.ActiveText)))
		{
			this.Title = string.Format("Repragator - Connected on ({0})", this.portComboBox.ActiveText);	
		}
	}

	protected void OnDisconnectButtonClicked (object sender, System.EventArgs e)
	{
		this.printer.Disconnect();
		
		this.Title = "Repragator";
	}

	protected void OnExtrudeButtonClicked (object sender, System.EventArgs e)
	{
		this.printer.Extrude(this.lengthSpinButton.Value, (int)this.feedrateSpinButton.Value);
	}

	protected void OnReverseButtonClicked (object sender, System.EventArgs e)
	{
		this.printer.Extrude(-this.lengthSpinButton.Value, (int)this.feedrateSpinButton.Value);
	}

	protected void OnHotendSetButtonClicked (object sender, System.EventArgs e)
	{
		this.printer.HotendTemp(Convert.ToInt32(this.hotendSpinButton.Value));
	}

	protected void OnHeatedbedSetButtonClicked (object sender, System.EventArgs e)
	{	
		this.printer.BedTemp(Convert.ToInt32(this.bedSpinButton.Value));
	}

	protected void OnHomeallButtonClicked (object sender, System.EventArgs e)
	{
		this.printer.Move("h", 0, 0);
	}

	protected void OnMotorsoffButtonClicked (object sender, System.EventArgs e)
	{
		this.printer.DisableMotors();
	}
	
	// Super janky way to save application settings :(
	protected void SaveSettings()
	{
		XmlDocument xmlDoc = new XmlDocument();
		
		XmlElement element, temp;
		XmlElement rootElement = xmlDoc.CreateElement("Settings");
		
		xmlDoc.AppendChild(rootElement);
		
		// printer settings
		element = xmlDoc.CreateElement("Printer");
		
		element.SetAttribute("Selected", this.baudComboBox.Active.ToString());
		
		foreach (object[] item in (ListStore)this.baudComboBox.Model)
		{
			temp = xmlDoc.CreateElement("Entry");
			
			temp.InnerText = item[0].ToString();
			
			element.AppendChild(temp);
		}
		
		rootElement.AppendChild(element);
		
		// extruder settings
		element = xmlDoc.CreateElement("Extruder");
		
		element.SetAttribute("Length", lengthSpinButton.Value.ToString());
		
		element.SetAttribute("Feedrate", feedrateSpinButton.Value.ToString());
		
		rootElement.AppendChild(element);
		
		// heater settings
		element = xmlDoc.CreateElement("Hotend");
		
		element.InnerText = this.hotendSpinButton.Value.ToString();
		
		rootElement.AppendChild(element);
		
		element = xmlDoc.CreateElement("Bed");
		
		element.InnerText = this.bedSpinButton.Value.ToString();
		
		rootElement.AppendChild(element);
		
		// axis settings
		element = xmlDoc.CreateElement("Axis");
		
		element.SetAttribute("XMovement", this.xMovement.Feedrate.ToString());
		
		element.SetAttribute("YMovement", this.yMovement.Feedrate.ToString());
		
		element.SetAttribute("ZMovement", this.zMovement.Feedrate.ToString());
		
		rootElement.AppendChild(element);
		
		// macros!
		element = xmlDoc.CreateElement("Macros");
		
		foreach (KeyValuePair<string, Macro> item in this.macros)
		{
			temp = xmlDoc.CreateElement("Item");
			
			temp.SetAttribute("Name", item.Key);
			
			temp.InnerText = item.Value.Text;
			
			element.AppendChild(temp);
		}
		
		rootElement.AppendChild(element);
		
		if (!Directory.Exists(this.userHome))
		{	
			Directory.CreateDirectory(this.userHome);
		}
		
		// janky work around for overwriting old settings, could use FileStream with StreamWriter to overwrite old file, i'm lazy :(
		if (File.Exists(this.userHome + "repragator.conf"))
		{
			File.Delete(this.userHome + "repragator.conf");	
		}
		
		using (StreamWriter writer = new StreamWriter(this.userHome + "repragator.conf"))
		{
			writer.Write(xmlDoc.InnerXml);
		}
	}
	
	protected void LoadSettings()
	{
		if (!File.Exists(this.userHome + "repragator.conf"))
			return;
		
		XmlElement rootElement, element;
		XmlDocument xmlDoc = new XmlDocument();
		
		using (StreamReader reader = new StreamReader(this.userHome + "repragator.conf"))
		{
			string xml = reader.ReadToEnd();
			
			xmlDoc.LoadXml(xml);
		}
		
		rootElement = xmlDoc.DocumentElement;
		
		// load printer settings
		element = (XmlElement)rootElement.GetElementsByTagName("Printer")[0];
		
		this.baudComboBox.Model = new ListStore(typeof(string));
		
		foreach (XmlElement child in element.ChildNodes)
		{
			this.baudComboBox.AppendText(child.InnerText);	
		}
		
		this.baudComboBox.Active = int.Parse(element.GetAttribute("Selected"));
		
		// load extruder settings
		element = (XmlElement)rootElement.GetElementsByTagName("Extruder")[0];
		
		this.lengthSpinButton.Value = double.Parse(element.GetAttribute("Length"));
		
		this.feedrateSpinButton.Value = double.Parse(element.GetAttribute("Feedrate"));
		
		// load heater settings
		element = (XmlElement)rootElement.GetElementsByTagName("Hotend")[0];
		
		this.hotendSpinButton.Value = double.Parse(element.InnerText);
		
		element = (XmlElement)rootElement.GetElementsByTagName("Bed")[0];
		
		this.bedSpinButton.Value = double.Parse(element.InnerText);
		
		// load axis settings
		element = (XmlElement)rootElement.GetElementsByTagName("Axis")[0];
		
		this.xMovement.Feedrate = int.Parse(element.GetAttribute("XMovement"));
		
		this.yMovement.Feedrate = int.Parse(element.GetAttribute("YMovement"));
		
		this.zMovement.Feedrate = int.Parse(element.GetAttribute("ZMovement"));
		
		// load macros
		element = (XmlElement)rootElement.GetElementsByTagName("Macros")[0];
		
		foreach (XmlElement child in element.ChildNodes)
		{
			this.macros.Add(child.GetAttribute("Name"), new Macro(child.GetAttribute("Name"), child.InnerText));	
		}
	}

	protected void OnSendButtonClicked (object sender, System.EventArgs e)
	{
		this.printer.SendCmd(this.commandEntry.Text);
	}

	protected void OnEditActionActivated (object sender, System.EventArgs e)
	{
		if (this.currentFile == null || this.currentFile == string.Empty)
			return;
		
		Editor editor = new Editor(false);
		
		editor.Title = this.currentFile;
		
		using (StreamReader reader = new StreamReader(this.currentFile))
		{
			editor.Text = reader.ReadToEnd();	
		}
		
		if (editor.Run() == (int)ResponseType.Accept)
		{
			using (StreamWriter writer = new StreamWriter(this.currentFile))
			{
				writer.Write(editor.Text);	
				
				writer.Flush();
			}
		}
		
		editor.Destroy();
	}

	protected void OnMacroEditActionActivated (object sender, System.EventArgs e)
	{
		MacroEditor editor = new MacroEditor();
		
		editor.Macros = this.macros;
		
		if (editor.Run() != (int)ResponseType.Accept)
		{
			editor.Destroy();
			
			return;
		}
		
		this.macros = editor.Macros;
		
		editor.Destroy();
	}

	protected void OnPrintButtonClicked (object sender, System.EventArgs e)
	{
		if (this.printButton.Label == "Print")
		{
			this.printer.Print();
			
			this.printButton.Label = "Pause";
		}
		else if (this.printButton.Label == "Pause")
		{
			this.printer.Pause();
			
			this.printButton.Label = "Resume";
		}
		else if (this.printButton.Label == "Restart")
		{
			this.printer.Print();
			
			this.printButton.Label = "Pause";
		}
		else
		{
			this.printer.Resume();
			
			this.printButton.Label = "Pause";
		}
	}

	protected void OnStopButtonClicked (object sender, System.EventArgs e)
	{
		this.printer.Stop();
		
		this.printButton.Label = "Restart";
	}
}